import { Tabs, Callout } from '@theguild/components'

# Subscriptions

Subscription is a third kind of GraphQL operation, next to Query and Mutation. Think of it as an event emitter where events are emitted by your backend and consumed by a GraphQL client-frontend in most scenarios.

GraphQL is just a specification and GraphQL Yoga or Apollo Server are Node implementations of the server-side part of GraphQL. A natural fit for Subscriptions is the WebSocket protocol, but it could be anything. We're going to focus only on WS and use [`subscriptions-transport-ws`](https://github.com/apollographql/subscriptions-transport-ws) package as our transport layer.

## Setup

For Queries and Mutations, we use `createExecution()` method to create an execution logic, in the case of Subscriptions it's very similar. We construct the subscription phase with `createSubscription()` method and provide it to a GraphQL server.

```ts
import { createApplication } from 'graphql-modules'

const application = createApplication({
  modules: [
    // ...
  ]
})

const subscribe = application.createSubscription()
```

<Callout>
  `createSubscription()` accepts an object with `subscribe` property in case you
  want to use your own subscription logic. It uses `subscribe` from `graphql` by
  default.
</Callout>

Why do we need it? GraphQL Modules need to understand the life cycle of a subscription to avoid memory leaks.

## Example

We recommend using [`graphql-subscriptions`](https://github.com/apollographql/graphql-subscriptions) as it provides a handful utility functions (filtering for example) and an event emitter.

Provide `PubSub` with an instance as the value so as a singleton service it becomes available across all modules.

```ts filename="application.ts"
import { createApplication } from 'graphql-modules'
import { PubSub } from 'graphql-subscriptions'
import { myModule } from './my-module'

const application = createApplication({
  modules: [myModule /* ... */],
  providers: [
    {
      provide: PubSub,
      useValue: new PubSub()
    }
  ]
})
```

Accessing `PubSub` in a module can be done both, in a resolve function and `Messages` service.

Take a look at two things here, how `MESSAGE_ADDED` event was emitter in `Messages.send()` and how `Subscriptions.messageAdded` subscribes to events.

```ts filename="my-module.ts"
import { createModule, Injectable, gql } from 'graphql-modules'
import { PubSub } from 'graphql-subscriptions'

const MESSAGE_ADDED = 'MESSAGE_ADDED'

const messages = [
  // ...
]

@Injectable()
class Messages {
  constructor(private pubsub: PubSub) {}

  async all() {
    return messages
  }

  async send(body: string) {
    const message = {
      id: generateRandomId(),
      body
    }

    messages.push(message)

    this.pubsub.publish(MESSAGE_ADDED, { messageAdded: message })

    return message
  }
}

export const myModule = createModule({
  id: 'my-module',
  providers: [Messages],
  typeDefs: gql`
    type Query {
      messages: [Message!]
    }

    type Mutation {
      sendMessage(message: String!): Message!
    }

    type Subscription {
      messageAdded: Message
    }

    type Message {
      id: ID!
      body: String!
    }
  `,
  resolvers: {
    Query: {
      messages(parent, args, ctx: GraphQLModules.Context) {
        return ctx.injector.get(Messages).all()
      }
    },
    Mutation: {
      sendMessage(parent, { message }, ctx: GraphQLModules.Context) {
        return ctx.injector.get(Messages).send(message)
      }
    },
    Subscription: {
      messageAdded: {
        subscribe(root, args, ctx: GraphQLModules.Context) {
          return ctx.injector.get(PubSub).asyncIterator([MESSAGE_ADDED])
        }
      }
    }
  }
})
```

Here are reference implementations of using GraphQL Subscriptions with WebSockets in both, Apollo Server and Express GraphQL.

<Tabs items={['GraphQL Yoga', 'Apollo Server', 'Express GraphQL']}>
<Tabs.Tab>

```ts filename="server.ts"
import 'reflect-metadata'
import { createServer } from '@graphql-yoga/node'
import { useGraphQLModules } from '@envelop/graphql-modules'
import { application } from './application'

const server = createServer({
  plugins: [useGraphQLModules(application)]
})

server.start().then(() => {
  console.log(`🚀 Server ready`)
})
```

<Callout>
  GraphQL Yoga uses
  [SSE](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events)
  by default, if you want to use WebSockets instead, follow [these
  instructions](https://www.graphql-yoga.com/docs/features/subscriptions).
</Callout>
</Tabs.Tab>

<Tabs.Tab>

```ts filename="server.ts"
import 'reflect-metadata'
import express from 'express'
import { createServer } from 'node:http'
import { SubscriptionServer } from 'subscriptions-transport-ws'
import { ApolloServer } from 'apollo-server-express'
import { application } from './application'

const schema = application.createSchemaForApollo()

const { schema, createExecution, createSubscription, createApolloExecutor } =
  application
const execute = createExecution()
const subscribe = createSubscription()

;(async function () {
  const app = express()

  const httpServer = createServer(app)

  let subscriptionServer
  const server = new ApolloServer({
    schema,
    executor: createApolloExecutor(),
    plugins: [
      {
        serverWillStart() {
          return {
            drainServer() {
              subscriptionServer.close()
            }
          }
        }
      }
    ]
  })

  subscriptionServer = SubscriptionServer.create(
    {
      schema,
      execute,
      subscribe
    },
    {
      server: httpServer,
      path: server.graphqlPath
    }
  )

  await server.start()
  server.applyMiddleware({ app })

  const PORT = 4000
  httpServer.listen(PORT, () =>
    console.log(`Server is now running on http://localhost:${PORT}/graphql`)
  )
})()
```

</Tabs.Tab>

<Tabs.Tab>

```ts filename="server.ts"
import 'reflect-metadata'
import express from 'express'
import graphqlHTTP from 'express-graphql'
import { createServer } from 'node:http'
import { SubscriptionServer } from 'subscriptions-transport-ws'
import { application } from './application'

const execute = application.createExecution()
const subscribe = application.createSubscription()
const { schema } = application

const server = express()

server.use(
  '/',
  graphqlHTTP({
    schema,
    customExecuteFn: execute,
    graphiql: true
  })
)

const webServer = createServer(app)

webServer.listen(4000, () => {
  console.log('🚀 Server ready at http://localhost:4000')

  new SubscriptionServer(
    {
      execute,
      subscribe,
      schema
    },
    {
      server: webServer,
      path: '/'
    }
  )
})
```

</Tabs.Tab>
</Tabs>
